<?php
/**
 * ep-calendar.php
 *
 * Encapsulates the calendar rendering logic for EventPress—allows rending multiple calendars together.
 *
 * @package EventPress
 * @since 0.2
 * @author Kunal Bhalla 
 */

/**
 * Generates a calendar from a given set of posts.
 *
 * @since 0.2
 */ 
class EP_Calendar {
	/**
	 * Counter for generating unique IDs for calendars on a single page.
	 * @static var int
	 * @since 0.2
	 */
	static $counter = 0;

	/**
	 * The date for the current view.
	 *
	 * @var int
	 * @since 0.2
	 */
	var $date;

	/**
	 * The week for the current view.
	 *
	 * @var int
	 * @since 0.2
	 */
	var $week;

	/**
	 * The start time for the current view.
	 *
	 * @var date
	 * @since 0.2
	 */
	var $start_time;

	/**
	 * The end time for the current view.
	 *
	 * @var date
	 * @since 0.2
	 */
	var $end_time;

	/**
	 * The current view type. 
	 *
	 * Possible values:
	 * 1. 'y' => Year
	 * 2. 'm' => Month
	 * 3. 'd' => Day
	 * 4. 'w' => Week
	 *
	 * @var char
	 * @since 0.2
	 */
	var $view;

	/**
	 * The query generated for wordpress to get_posts from.
	 *
	 * @var string
	 * @since 0.2
	 */
	var $query;

	/**
	 * Modify controls displayed by default.
	 */
	var $controls;

	/**
	 * Custom query passed in as argument.
	 *
	 * @since 0.2
	 */
	var $custom_query;

	/**
	 * Events returned after doing the query.
	 *
	 * @since 0.2
	 */
	var $events;

	/**
	 * ID for the table generated.
	 *
	 * @since 0.2
	 */
	var $disablecss;

	/**
	 * Initiate the calendar for the date specified.
	 *
	 * @param array $args @see update_args
	 */
	function EP_Calendar( $args = Array() ) {
		$this->update_args( $args );
		$this->generate_query();
		$this->populate_events();

		if( !$this->disablecss )
			$this->render_css();

		//Use this if you only want to over-ride _some_ of the css rules
		do_action( 'ep-cal-custom-css', $this->id ); 

		$this->render_html();

		self::$counter++;
	}

	/**
	 * Update the arguments.
	 *
	 * $args = Array(
	 *     'view' => 'm' [| 'd' | 'y' | 'w'],
	 *     'date' => current_time(),
	 *     'controls' => Array( 'nextprev', 'swap_view' ),
	 *     'custom_query => Array( Custom query parameters you might want to pass in ),
	 *     'pagination_query' => Pass in string generated by next/prev links here (can avoid view/data, accordingly)
	 *     'id' => CSS ID for the calendar table, defaults to ep-cal-[counter],
	 *     'disablecss' => Disable the default inline CSS provided? 
	 * )
	 *
	 * @param array $args
	 */
	function update_args( $args ) {
		if( !empty( $args ) )
			extract( $args );

		if( !isset( $view ) ) $this->view = 'm';
		else $this->view = $view;

		if( !isset( $date ) ) $this->date = current_time( 'timestamp' );
		else $this->date = $date;

		if( !isset( $controls ) ) $this->controls = Array( 'nextprev', 'swap_view', 'title' );
		else $this->controls = $controls;

		if( !isset( $custom_query ) ) $this->custom_query = Array();
		else $this->custom_query = $custom_query;

		if( !isset( $this->pagination_query ) ) $this->pagination_query = false;
		else {
			// Of the form 'd=20012011&v=m'
			$pq = wp_parse_args( $this->pagination_query );
			if( isset( $pq['d'] ) )
				$this->date = $pq['d'];
			if( isset( $pq['v'] ) )
				$this->view = $pq['v'];
		}

		if( !isset( $id ) ) $this->id = 'ep-cal-' . self::$counter;
		else $this->id = $id;

		if( !isset( $disablecss ) ) $this->disablecss = false;
		else $this->disablecss = true;

		$this->view = apply_filters( 'ep-calendar-view', $this->view );
		$this->date = apply_filters( 'ep-calendar-date', $this->date );
		$this->controls = apply_filters( 'ep-calendar-controls', $this->controls );
		$this->id = apply_filters( 'ep-calendar-id', $this->id );
		$this->disablecss = apply_filters( 'ep-calendar-disablecss', $this->disablecss );

		$this->date = getdate( $this->date );
		
		$weekstart = get_option( 'start_of_week' );
		$weekoffset = $this->date['wday'] - $weekstart;
		if( $weekoffset < 0 ) { $endweekoffset = -1*$weekoffset; $weekoffset = 7-$endweekoffset; }
		else { $endweekoffset = 7 - $weekoffset; }

		switch( $this->view ) {
			case 'm':
				$this->start_time = getdate( mktime( $this->date['hours'], $this->date['minutes'], $this->date['seconds'], $this->date['mon'], 1, $this->date['year'] ) );
				$this->end_time = getdate( mktime( $this->date['hours'], $this->date['minutes'], $this->date['seconds'], $this->date['mon'], $this->_get_last_mday( $this->date['mon'], $this->date['year'] ), $this->date['year'] ) );
				break;
			case 'y':
				$this->start_time = getdate( mktime( $this->date['hours'], $this->date['minutes'], $this->date['seconds'], 1, 1, $this->date['year'] ) );
				$this->end_time = getdate( mktime( $this->date['hours'], $this->date['minutes'], $this->date['seconds'], 12, 31, $this->date['year'] ) );
				break;
			case 'd':
				$this->start_time = getdate( mktime( 0, 0, 0, $this->date['mon'], $this->date['mday'], $this->date['year'] ) );
				$this->end_time = getdate( mktime( 23, 59, 59, $this->date['mon'], $this->date['mday'], $this->date['year'] ) );
				break;
			case 'w':
				$this->start_time = getdate( strtotime( "last " . $this->_get_day_name( $weekstart ), $this->date[0] ) );
				$this->end_time = getdate( strtotime( "next " . $this->_get_day_name( ($weekstart+6)%7 ), $this->date[0] ) );
				break;
		}
	}

	/**
	 * Generate the query data.
	 */
	function generate_query() {
		$this->query = Array(
			'post_type' => 'ep_event',
			'meta_query' => Array(
				Array(
					'key' => '_ep_end',
					'value' => $this->start_time[0],
					'compare' => '>=',
					'type' => 'numeric'
				),
				Array(
					'key' => '_ep_start',
					'value' => $this->end_time[0],
					'compare' => '<=',
					'type' => 'numeric'
				)
			),
			'orderby' => '_ep_start',
			'order' => 'ASC'
		);

		$this->query = apply_filters( 'ep-calendar-query', array_merge( $this->query, $this->custom_query ) );
	}

	/**
	 * Get the list of events required for this page.
	 */
	function populate_events() {
		$this->events = get_posts( $this->query );
		$this->events = apply_filters( 'ep-calendar-events', $this->events );
	}

	/**
	 * Renders the results in HTML
	 */
	function render_html() {
		switch( $this->view ) {
			case 'm': $this->_render_month(); break;
			case 'w': $this->_render_week(); break;
			case 'y': $this->_render_year(); break;
			case 'd': $this->_render_day(); break;
		};
	}

	function _render_month() {

		$weekstart = get_option( 'start_of_week' );
		echo "<table class = 'ep-calendar' id = '{$this->id}'>";

		if( in_array( 'title', $this->controls ) ) 
			echo "<caption class = 'ep-cal-caption'>" . $this->_get_i18n_mon_name( $this->date['mon'] ) . "</caption>";

		echo "<colgroup>";
			for( $i=0; $i<7; $i++ )
				echo "<col class = 'ep-col-day" . ($i+$weekstart)%7 . "'>";
		echo "</colgroup>";

		echo "<thead class = 'ep-cal-thead'>";
			echo "<tr>";
			for( $i=0; $i<7; $i++ )
				echo "<th scope='col'>" . $this->_get_i18n_day_name( ($i+$weekstart) % 7 ) . "</th>";
			echo "</tr>";
		echo "</thead>";


		echo "<tbody class = 'ep-cal-tbody'>";
			$daycounter = 0;
			$cellcounter = 0;
			$eventcounter = 0;
			$eventqueue = Array();
			$nexteventqueue = Array();

			while( $daycounter < $this->_get_last_mday( $this->date['mon'], $this->date['year'] ) || $cellcounter%7 != 0 ) {

				if( $cellcounter%7 == 0 ) {
					foreach( $eventqueue as $eventc ) {
						$st = getdate( get_post_meta( $this->events[$eventc]->ID, '_ep_start', true ) );
						$en = getdate( get_post_meta( $this->events[$eventc]->ID, '_ep_end', true ) );

						$weekst = getdate( mktime( 00, 00, 00, $this->date['mon'], $daycounter-6 , $this->date['year'] ) );
						$weeken = getdate( mktime( 23, 59, 59, $this->date['mon'], $daycounter , $this->date['year'] ) );

						if( $weekst[0] > $st[0] ) {//Starts from beginning of week
							$prefix = 0;
							$starti = "<span class = 'ep-cal-cli'>&nbsp;</span>";
						} else {
							$prefix = $st['mday']-$weekst['mday'];
							$starti = "<span class = 'ep-cal-st-indicator'>&nbsp;</span>";
						}
						
						if( $weeken[0] < $en[0] ) { //Goes beyond end of the week
							$suffix = 0;
							$nexteventqueue[] = $eventc;
							$endi = "<span class = 'ep-cal-cri'>&nbsp;</span>";
						}
						else {
							$suffix = $weeken['mday'] - $en['mday'];
							$endi = "<span class = 'ep-cal-en-indicator'>&nbsp;</span>";
						}

						$length = 7 - $prefix - $suffix;
						
						echo "<tr class = 'ep-cal-erow'>";
							while( $prefix > 0 ) {
								echo "<td class = 'ep-cal-span'>&nbsp;</td>";
								$prefix--;
							}
							echo "<td colspan = '$length' class = 'ep-cal-event'>{$starti}<a href = '" .get_permalink($this->events[$eventc]->ID). "'>{$this->events[$eventc]->post_title}</a>{$endi}</td>";
							while( $suffix > 0 ) {
								echo "<td class = 'ep-cal-span'>&nbsp;</td>";
								$suffix--;
							}
						echo "</tr>";
					}
					$eventqueue = $nexteventqueue;
					$nexteventqueue = Array();
					echo "<tr>";
				}

				echo "<td class = 'ep-cal-day'>";
				if( ( $daycounter != 0 ) || ( ($cellcounter+$weekstart) % 7 == $this->start_time['wday'] ) )
					$daycounter++;

				if( $daycounter != 0 && $daycounter <= $this->_get_last_mday( $this->date['mon'], $this->date['year'] ) )
					echo $daycounter;
				
				if( isset( $this->events[ $eventcounter ] ) ) {
					$topevent_start = ( get_post_meta( $this->events[ $eventcounter ]->ID, '_ep_start', true ) );
					$topevent_end = ( get_post_meta( $this->events[ $eventcounter ]->ID, '_ep_end', true ) );
				}

				$curdate_start = ( mktime( 00, 00, 00, $this->date['mon'], $daycounter+1 , $this->date['year'] ) );
				$curdate_end = ( mktime( 23, 59, 59, $this->date['mon'], $daycounter+1 , $this->date['year'] ) );

				while( isset( $this->events[ $eventcounter ] ) &&
				       $topevent_start <= $curdate_end && 
				       $topevent_end >= $curdate_start
				     ) {

					$eventqueue[] = $eventcounter++;

					if( isset( $this->events[ $eventcounter ] ) ) {
						$topevent_start = ( get_post_meta( $this->events[ $eventcounter ]->ID, '_ep_start', true ) );
						$topevent_end = ( get_post_meta( $this->events[ $eventcounter ]->ID, '_ep_end', true ) );
					} 
				}

				echo "</td>";

				if( $cellcounter%7 == 6 ) {
					echo "</tr>";
				}

				$cellcounter++;
			}
		echo "</tbody>";

		echo "</table>";
	}

	function _render_week() {
	}

	function _render_year() {
	}

	function _render_day() {
	}

	/**
	 * Generate the id targeted CSS to over-ride default theme controls.
	 */
	function render_css() {
		switch( $this->view ) {
			case 'm': $this->_render_css_month(); break;
			case 'w': $this->_render_css_week(); break;
			case 'y': $this->_render_css_year(); break;
			case 'd': $this->_render_css_day(); break;
		};
	}

	function _render_css_month() {
	echo <<<CSS
		<style type = 'text/css'>
			/* Nukes default CSS styling for table. A bit hacky, but this is the only solution. */
			table#{$this->id}, table#{$this->id} caption, table#{$this->id} tbody, table#{$this->id} tfoot,
			table#{$this->id} thead, table#{$this->id} th, table#{$this->id} tr, table#{$this->id} td
			{ margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; }

			table#{$this->id} {
				width: 98%;
				border: solid 1px #aaa;
			}

			table#{$this->id} caption {
				font-size: 2em;
				font-weight: bold;
				padding-bottom: .5em;
			}

			table#{$this->id} th {
				text-align: center;
				font-size: .75em;
				font-variant: small-caps;
				font-weight: bold;
				border-bottom: solid 2px #aaa;
				background-color: #fff;
			}

			table#{$this->id} td {
				width: 12%;
				padding: 0 1%;
			}

			table#{$this->id} td.ep-cal-day {
				text-align: right;
				border-top: solid #aaa 1px;
				border-left: solid #aaa 1px;
				border-right: solid #aaa 1px;
			}
			
			table#{$this->id} td.ep-cal-event {
				background-color: #777;
				color: #fff;
				border-left: solid #000 1px;
				border-right: solid #000 1px;
				font-size: .9em;
				padding: 0 1%;
			}

			table#{$this->id} td.ep-cal-event a:link, 
			table#{$this->id} td.ep-cal-event a:visited {
				color: #fff;
				text-decoration: none;
			}

			table#{$this->id} td.ep-cal-event a:active, 
			table#{$this->id} td.ep-cal-event a:hover {
				text-decoration: underline;
			}

			table#{$this->id} td.ep-cal-span {
				border-left: solid #aaa 1px;
				border-right: solid #aaa 1px;
				padding: 0%;
			}

			.ep-cal-cli {
				border-top: solid transparent .4em;
				border-bottom: solid transparent .4em;
				border-left: none;
				border-right: solid #fff 1em;
				float: left;
				display: block;
				width: 0;
				margin-top: .5em;
				margin-bottom: .1em;
				margin-right: 1em;
				height: 0;
				width: 0;
			}

			.ep-cal-cri {
				border-top: solid transparent .4em;
				border-bottom: solid transparent .4em;
				border-right: none;
				border-left: solid #fff 1em;
				float: right;
				display: block;
				margin-top: .5em;
				margin-bottom: .1em;
				margin-left: 1em;
				height: 0;
				width: 0;
			}
		</style>
CSS;
	}

	function _render_css_week() {
	}

	function _render_css_year() {
	}

	function _render_css_day() {
	}

	/**
	 * Returns a string that can be passed around using Get and be used to
	 * generate the next calendar.
	 */
	function next_cal() {
	}

	/**
	 * Returns a string that can be passed around using Get and be used to
	 * generate the previous calendar.
	 */
	function prev_cal() {
	}

	/**
	 * Helper function: get the last day of the month.
	 *
	 * Yes, this is ugly.
	 */
	function _get_last_mday( $mon, $year ) {
		switch( $mon ) {
			case 1: return 31;
			case 2: if( $year % 400 == 0 )
					return 29;
				else if( $year % 100 == 0 )
					return 28;
				else if( $year % 4 == 0 )
					return 29;
				else
					return 28;
			case 3: return 31;
			case 4: return 30;
			case 5: return 31;
			case 6: return 30;
			case 7: return 31;
			case 8: return 31;
			case 9: return 30;
			case 10: return 31;
			case 11: return 30;
			case 12: return 31;
		};
	}

	/**
	 * Helper function: get name of day.
	 *
	 * NOTE—NOT TO BE TRANSLATED as used in PHP to get week ranges.
	 * Not used in any printing.
	 */
	function _get_day_name( $day ) {
		switch( $day ) {
			case 0: return 'Sunday';
			case 1: return 'Monday';
			case 2: return 'Tuesday';
			case 3: return 'Wednesday';
			case 4: return 'Thursday';
			case 5: return 'Friday';
			case 6: return 'Saturday';
		}
	}

	function _get_i18n_day_name( $day ) {
		$days = Array( 
			__( 'Sunday', 'eventpress' ),
			__( 'Monday', 'eventpress' ),
			__( 'Tuesday', 'eventpress' ),
			__( 'Wednesday', 'eventpress' ),
			__( 'Thursday', 'eventpress' ),
			__( 'Friday', 'eventpress' ),
			__( 'Saturday', 'eventpress' )
		);
		return $days[ $day ];
	}

	function _get_i18n_mon_name( $mon ) {
		$months = Array(
			__( 'January', 'eventpress' ),
			__( 'February', 'eventpress' ),
			__( 'March', 'eventpress' ),
			__( 'April', 'eventpress' ),
			__( 'May', 'eventpress' ),
			__( 'June', 'eventpress' ),
			__( 'July', 'eventpress' ),
			__( 'August', 'eventpress' ),
			__( 'September', 'eventpress' ),
			__( 'October', 'eventpress' ),
			__( 'November', 'eventpress' ),
			__( 'December', 'eventpress' )
		);
		return $months[ $mon-1 ];
	}
}
